﻿using System;
using System.Text;

namespace _443_Server
{
	public class Stream
	{
		#region Declarations
		private byte[] buffer;
		private int bufferLocation;
		private static int[] bitMaskOut = new int[32];
		private int bitPosition;
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the buffer the stream is holding.
		/// </summary>
		public byte[] Buffer
		{
			get { return buffer; }
			set { buffer = value; }
		}

		/// <summary>
		/// Gets or sets where the buffer is going to read from or write to.
		/// </summary>
		public int BufferLocation
		{
			get { return bufferLocation; }
			set { bufferLocation = value; }
		}

		/// <summary>
		/// Gets or sets where the bit-wise methods will read from or write to.
		/// </summary>
		public int BitPosition
		{
			get { return bitPosition; }
			set { bitPosition = value; }
		}

		/// <summary>
		/// Gets the bit mask out byte array.
		/// </summary>
		public static int[] BitMaskOut
		{
			get { return bitMaskOut; }
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Creates a new buffer with the size of 5000 bytes.
		/// </summary>
		public Stream()
		{
			buffer = new byte[5000];
			bufferLocation = 0;

			for (int i = 0; i < 32; i++)
				bitMaskOut[i] = (1 << i) - 1;
		}

		/// <summary>
		/// Creates a new stream from the specified buffer.
		/// </summary>
		/// <param name="_buffer">The buffer to create the stream from.</param>
		public Stream(byte[] _buffer)
		{
			this.buffer = _buffer;
			this.bufferLocation = 0;

			for (int i = 0; i < 32; i++)
				bitMaskOut[i] = (byte)((1 << i) - 1);
		}
		#endregion

		#region ReadMethods
		/// <summary>
		/// Reads a single byte and advances the buffer location by 1.
		/// </summary>
		/// <returns>A single byte.</returns>
		public byte ReadByte()
		{
			return buffer[bufferLocation++];
		}

		/// <summary>
		/// Reads two bytes and advances the buffer location by 2.
		/// </summary>
		/// <returns>A big-endian short.</returns>
		public ushort ReadWord()
		{
			return (ushort)(buffer[bufferLocation++] * 0x100 + buffer[bufferLocation++]);
		}

		/// <summary>
		/// Reads four bytes and advances the buffer location by 4.
		/// </summary>
		/// <returns>A big-endian dword.</returns>
		public int ReadDWord()
		{
			return (buffer[bufferLocation++] << 24) + (buffer[bufferLocation++] << 16) + (buffer[bufferLocation++] << 8) + buffer[bufferLocation++];
		}

		/// <summary>
		/// Reads eight bytes and advances the buffer location by 8.
		/// </summary>
		/// <returns>A big-endian qword.</returns>
		public long ReadQWord()
		{
			//return (buffer[bufferLocation++] << 56) + (buffer[bufferLocation++] << 48) + (buffer[bufferLocation++] << 40) + (buffer[bufferLocation++] << 32) + (buffer[bufferLocation++] << 24) + (buffer[bufferLocation++] << 16) + (buffer[bufferLocation++] << 8) + buffer[bufferLocation++];
			uint i = (uint)ReadDWord() << 32;
			return i + (uint)ReadDWord();
		}

		/// <summary>
		/// Reads a single byte and advances the buffer location by 1.
		/// </summary>
		/// <returns>A special formatted byte.</returns>
		public byte ReadByteA()
		{
			return (byte)(buffer[bufferLocation++] - 128);
		}

		/// <summary>
		/// Reads two formatted bytes and advances the buffer location by 2.
		/// </summary>
		/// <returns>A big-endian modified short.</returns>
		public ushort ReadWordS()
		{
			return (ushort)((buffer[bufferLocation++] << 8) + (buffer[bufferLocation++] + 128));
		}

		public int ReadSmartWord()
		{
			int i = buffer[bufferLocation] & 0xff;
			if (i < 128)
			{
				return ReadByte();
			}
			else
			{
				return ReadWord() - 32768;
			}
		}

		public int ReadSignedByteA()
		{
			bool bo = (byte)(buffer[bufferLocation] - 128) >= 128;
			byte b = (byte)(buffer[bufferLocation++] - 128);
			if (bo)
				return -(256 - b);
			else
				return b;
		}

		public int ReadSignedWordA()
		{
			int i = ReadByte() << 8;
			i += ReadByteA();
			return i;
		}

		public short ReadSignedWord()
		{
			bufferLocation += 2;
			int i = ((buffer[bufferLocation - 2] & 0xff) << 8) + (buffer[bufferLocation - 1] & 0xff);
			if (i > 32767)
				i -= 0x10000;
			return (short)i;
		}

		public short ReadSignedWordLittleEndian()
		{
			bufferLocation += 2;
			int i = ((buffer[bufferLocation - 1] & 0xff) << 8) + (buffer[bufferLocation - 2] & 0xff);
			if (i > 32767)
				i -= 0x10000;
			return (short)i;
		}

		public short ReadSignedWordLittleEndianA()
		{
			bufferLocation += 2;
			int i = ((buffer[bufferLocation - 1] & 0xff) << 8) + (buffer[bufferLocation - 2] - 128 & 0xff);
			if (i > 32767)
				i -= 0x10000;
			return (short)i;
		}

		public sbyte ReadSignedByte()
		{
			return (sbyte)buffer[bufferLocation++];
		}

		/// <summary>
		/// Reads a single byte and advances the buffer location by 1.
		/// </summary>
		/// <returns>A special formatted byte.</returns>
		public byte ReadByteC()
		{
			return (byte)(-buffer[bufferLocation++]);
		}

		/// <summary>
		/// Reads a single byte and advances the buffer location by 1.
		/// </summary>
		/// <returns>A special formatted byte.</returns>
		public byte ReadByteS()
		{
			return (byte)(128 - buffer[bufferLocation++]);
		}

		/// <summary>
		/// Reads an amount of bytes and returns a byte array containing the read values. Increases buffer location.
		/// </summary>
		/// <param name="count">The amount of bytes to read.</param>
		/// <returns>The read bytes in a buffer array.</returns>
		public byte[] ReadBytes(int count)
		{
			byte[] b = new byte[count];
			Array.Copy(buffer, bufferLocation, b, 0, count);
			bufferLocation += count;
			return b;
		}

		/// <summary>
		/// Reads bytes until encountering value 10 and reads another byte. Increases buffer location.
		/// </summary>
		/// <returns>The string read.</returns>
		public string ReadString()
		{
			int cl = bufferLocation;
			while (buffer[bufferLocation++] != 0xA && bufferLocation < buffer.Length - 1) ; //0xA is the Jagex end-of-string value
			if (cl == bufferLocation)
				return "";
			return ASCIIEncoding.ASCII.GetString(buffer, cl, (bufferLocation - cl) - 1);
		}

		public string ReadNewString()
		{
			int cl = bufferLocation;
			while (buffer[bufferLocation++] != 0 && bufferLocation < buffer.Length - 1) ; //0xA is the Jagex end-of-string value
			if (cl == bufferLocation)
				return "";
			return ASCIIEncoding.ASCII.GetString(buffer, cl, (bufferLocation - cl) - 1);
		}

		/// <summary>
		/// Reads two bytes and advances the buffer location by 2.
		/// </summary>
		/// <returns>A little-endian short.</returns>
		public ushort ReadWordLittleEndian()
		{
			return (ushort)(buffer[bufferLocation++] + buffer[bufferLocation++] * 0x100);
		}

		/// <summary>
		/// Reads two formatted bytes and advances the buffer location by 2.
		/// </summary>
		/// <returns>A little-endian short.</returns>
		public ushort ReadWordLittleEndianA()
		{
			return (ushort)((buffer[bufferLocation++] - 128) + buffer[bufferLocation++] * 0x100);
		}

		/// <summary>
		/// Reads two formatted bytes and advances the buffer location by 2.
		/// </summary>
		/// <returns>A little-endian short.</returns>
		public ushort ReadWordA()
		{
			return (ushort)((buffer[bufferLocation++] << 8) + (buffer[bufferLocation++] - 128));
		}

		/// <summary>
		/// Reads two formatted bytes and advances the buffer location by 2.
		/// </summary>
		/// <returns>A little-endian short.</returns>
		public ushort ReadWordC()
		{
			return (ushort)((buffer[bufferLocation++] << 8) + (buffer[bufferLocation++] + 128));
		}

		/// <summary>
		/// Reads formatted and reversed bytes.
		/// </summary>
		/// <param name="dest">The buffer to write the bytes to.</param>
		/// <param name="offset">The offset of the bytes to start at.</param>
		/// <param name="length">The length of the bytes to write.</param>
		public void ReadBytesReverseA(byte[] dest, int offset, int length)
		{
			for (int k = (length + offset) - 1; k >= length; k--)
				dest[k] = (byte)(buffer[bufferLocation++] - 128);
		}

		/// <summary>
		/// Reads reversed bytes.
		/// </summary>
		/// <param name="dest">The buffer to write the bytes to.</param>
		/// <param name="offset">The offset of the bytes to start at.</param>
		/// <param name="length">The length of the bytes to write.</param>
		public void ReadBytesReverse(byte[] dest, int offset, int length)
		{
			for (int k = (length + offset) - 1; k >= length; k--)
				dest[k] = (byte)(buffer[bufferLocation++]);
		}

		/// <summary>
		/// Reads a single byte and does not advance the buffer location.
		/// </summary>
		/// <returns>A single byte.</returns>
		public byte PeakByte()
		{
			return buffer[bufferLocation];
		}

		public void Skip(int l)
		{
			this.bufferLocation += l;
		}
		#endregion

		#region WriteMethods
		/// <summary>
		/// Writes a single byte to the buffer and advances the buffer location by 1.
		/// </summary>
		/// <param name="value">The value to write</param>
		public void WriteByte(byte value)
		{
			buffer[bufferLocation++] = value;
		}

		/// <summary>
		/// Writes a big-endian short to the buffer and advances the buffer location by 2.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteWord(ushort value)
		{
			buffer[bufferLocation++] = (byte)(value >> 8);
			buffer[bufferLocation++] = (byte)value;
		}

		/// <summary>
		/// Writes a big-endian dword to the buffer and advances the buffer location by 4.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteDWord(int value)
		{
			buffer[bufferLocation++] = (byte)(value >> 24);
			buffer[bufferLocation++] = (byte)(value >> 16);
			buffer[bufferLocation++] = (byte)(value >> 8);
			buffer[bufferLocation++] = (byte)value;
		}

		/// <summary>
		/// Writes a big-endian qword to the buffer and advances the buffer location by 8.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteQWord(long value)
		{
			buffer[bufferLocation++] = (byte)(value >> 56);
			buffer[bufferLocation++] = (byte)(value >> 48);
			buffer[bufferLocation++] = (byte)(value >> 40);
			buffer[bufferLocation++] = (byte)(value >> 32);
			buffer[bufferLocation++] = (byte)(value >> 24);
			buffer[bufferLocation++] = (byte)(value >> 16);
			buffer[bufferLocation++] = (byte)(value >> 8);
			buffer[bufferLocation++] = (byte)value;
		}

		/// <summary>
		/// Writes the string to the buffer, followed by another value. Increases the buffer location.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteString(string value)
		{
			for (int i = 0; i < value.Length; i++)
				buffer[bufferLocation++] = (byte)value[i];
			buffer[bufferLocation++] = 0;
		}

		/// <summary>
		/// Writes a formatted byte to the buffer and increases the buffer location by one.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteByteA(byte value)
		{
			buffer[bufferLocation++] = (byte)(value + 128);
		}

		/// <summary>
		/// Writes a formatted byte to the buffer and increases the buffer location by one.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteByteC(byte value)
		{
			buffer[bufferLocation++] = (byte)-value;
		}

		/// <summary>
		/// Writes a formatted byte to the buffer and increases the buffer location by one.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteByteS(byte value)
		{
			buffer[bufferLocation++] = (byte)(128 - value);
		}

		/// <summary>
		/// Writes an array of bytes to the buffer. Increases the buffer location.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteBytes(byte[] value)
		{
			for (int i = 0; i < value.Length; i++)
				buffer[bufferLocation++] = value[i];
		}

		public void WriteBytesA(byte[] value, int offset, int length)
		{
			for (int i = offset; i < length + offset; i++)
				WriteByteA(value[i]);
		}

		/// <summary>
		/// Writes a chopped array of bytes to the buffer. Increases the buffer location.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="length">The amount of bytes to write</param>
		/// <param name="offset"></param>
		public void WriteBytes(byte[] value, int count, int offset)
		{
			for (int i = offset; i < offset + count; i++)
				buffer[bufferLocation++] = value[i];
		}

		/// <summary>
		/// Writes a little-endian short to the buffer and advances the buffer location by 2.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteWordLittleEndian(ushort value)
		{
			buffer[BufferLocation++] = (byte)value;
			buffer[BufferLocation++] = (byte)(value >> 8);
		}

		/// <summary>
		/// Writes a formatted little-endian short to the buffer and advances the buffer location by 2.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteWordLittleEndianA(ushort value)
		{
			buffer[BufferLocation++] = (byte)(value + 128);
			buffer[BufferLocation++] = (byte)(value >> 8);
		}

		/// <summary>
		/// Writes a reversed array of bytes to the buffer. Increases the buffer location.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteBytesReversed(byte[] value)
		{
			for (int i = value.Length - 1; i > -1; i--)
				buffer[bufferLocation++] = value[i];
		}

		/// <summary>
		/// Writes a formatted reversed array of bytes to the buffer. Increases the buffer location.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteBytesReversedA(byte[] value, int length)
		{
			if (length > value.Length)
				length = value.Length;
			for (int i = length - 1; i > -1; i--)
				buffer[bufferLocation++] = (byte)(value[i] + 128);
		}

		/// <summary>
		/// Writes a formatted big-endian short to the buffer and advances the buffer location by 2.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteWordA(ushort value)
		{
			buffer[bufferLocation++] = (byte)(value >> 8);
			buffer[bufferLocation++] = (byte)(value + 128);
		}

		/// <summary>
		/// Writes a little-endian integer to the buffer and advances the buffer location by 4.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteDWordLittleEndian(int value)
		{
			buffer[bufferLocation++] = (byte)(value);
			buffer[bufferLocation++] = (byte)(value >> 8);
			buffer[bufferLocation++] = (byte)(value >> 16);
			buffer[bufferLocation++] = (byte)(value >> 24);
		}

		/// <summary>
		/// Writes a mixed-endian integer to the buffer and advances the buffer location by 4.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteDWordMixedEndian(int value)
		{
			buffer[bufferLocation++] = (byte)(value >> 16);
			buffer[bufferLocation++] = (byte)(value >> 24);
			buffer[bufferLocation++] = (byte)(value);
			buffer[bufferLocation++] = (byte)(value >> 8);
		}

		/// <summary>
		/// Writes a mixed-endian integer to the buffer and advances the buffer location by 4.
		/// </summary>
		/// <param name="value">The value to write.</param>
		public void WriteDWordMixedEndian2(int value)
		{
			buffer[bufferLocation++] = (byte)(value >> 8);
			buffer[bufferLocation++] = (byte)(value);
			buffer[bufferLocation++] = (byte)(value >> 24);
			buffer[bufferLocation++] = (byte)(value >> 16);
		}
		#endregion

		#region BitMethods
		/// <summary>
		/// Initializes bit access.
		/// </summary>
		public void InitBitAccess()
		{
			bitPosition = bufferLocation * 8;
		}

		/// <summary>
		/// Finishes bit access.
		/// </summary>
		public void FinishBitAccess()
		{
			bufferLocation = (bitPosition + 7) / 8;
		}

		/// <summary>
		/// Writes bits to the buffer. Bit access must be initialized.
		/// </summary>
		/// <param name="numBits">The number of bits to write.</param>
		/// <param name="value">The value to write.</param>
		public void WriteBits(int numBits, int value) //Lets thank winterLove servers for this. Saves me some time.
		{
			int bytePos = bitPosition >> 3;
			int bitOffset = 8 - (bitPosition & 7);
			bitPosition += numBits;

			for (; numBits > bitOffset; bitOffset = 8)
			{
				buffer[bytePos] &= (byte)~bitMaskOut[bitOffset];
				buffer[bytePos++] |= (byte)((value >> (numBits - bitOffset)) & bitMaskOut[bitOffset]);

				numBits -= bitOffset;
			}
			if (numBits == bitOffset)
			{
				buffer[bytePos] &= (byte)~bitMaskOut[bitOffset];
				buffer[bytePos] |= (byte)(value & bitMaskOut[bitOffset]);
			}
			else
			{
				buffer[bytePos] &= (byte)~(bitMaskOut[numBits] << (bitOffset - numBits));
				buffer[bytePos] |= (byte)((value & bitMaskOut[numBits]) << (bitOffset - numBits));
			}
		}
		#endregion
	}
}